package jmpo.mth;

import java.lang.Thread;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * Многозадачность в операционной системе может быть реализована на двух уровнях:
 *     process - это основной элемент исполнения в ОС, каждый такой <<процесс>>
 *     работает в изолированном окружении, чтобы процессы могли взаимодействовать
 *     необходимы специальные ресурсы операционной системы, например каналы связи.
 *     Процессы могут взаимодействовать только через посредство ОС!
 *     
 *     thread - это элемент исполнения внутри процесса, основное его отличие от 
 *     процесса состоит в возможности свободно распоряжаться всеми ресурсами 
 *     родительского процесса без необходимости обращаться к системе, кроме того
 *     при переключениии работы с одного thread на другой не происходит переключения
 *     системного контекста, например не нужно перезагружать настройки безопасности 
 *     доступа к памяти. Это позволяет крайне эффективно производить вычисления на 
 *     большом количестве исполняющих элементов при работе над общими данными. 
 *     Взаимодействие и разделение ресурсов между thread - это программная задача и 
 *     она не обеспечивается ОС!
 *     По-русски thread называют <<потоком>> или <<нитью исполнения>>.
 * 
 *     green-thread или virtual thread - это программный элемент исполнения, котороый 
 *     никак не виден со стороны ОС. Если для процессов ОС создаёт отдельный реестровый 
 *     контектс и умеет их переключать и запоминать их состояние в стеке, то про green-thread
 *     ОС ничего не знает, обработкой их состояния, запуском и остановкой занимается сама 
 *     программа. Выгода от таких элементов исполнения состоит в том, что их можно создать
 *     в очень большом количестве, не занимая реестровый контекст системы, а размещая их 
 *     контектс в куче (динамической памяти). Этот подход может оказаться эффективным, если 
 *     каждый green-thread большую часть времени ждёт, а исполняется только незначительную 
 *     часть времени. При большом количестве green-thread (обычно это 1'000 - 100'000) в 
 *     каждый момент времени находится достаточно много green-thread в состоянии готовности,
 *     чтобы исполнять код, причём с точки зрения ОС продолжает исполняться тот же самый 
 *     system thread (системный поток) без переключения контекста.  
 */

/**
 * В Java есть библиотечные возможности запуска системных потоков исключительно средствами 
 * языка Java, без явных системных вызовов.
 */
final class ThreadInheritanceExample {
    /**
     * Один из способов создания нового элемента исполнения в рамках текущего потока -
     * это расширение библиотечного класса Thread.
     * Мы должны переопределить метод run.
     * Метод run не возвращает и не принимает параметров.
     * Если нам необходимы какие-либо данные для исполнения потока, то
     * мы определяем их во внутренних данных класса.
     * 
     * Обратим внимание, что данные класса MyThread - разделяемые!
     * Чтобы избежать случайного изменения этих данных в процессе 
     * поддержки кода, они намеренно снабжены квалификатором final.
     */
    private static final class MyThread extends Thread {
        public MyThread(String line, long repeat) {
            this.line = line;
            this.repeat = repeat;
        }

        @Override
        public void run() {
            for (long cnt = 0l; cnt != repeat; ++cnt) 
                System.out.format("Line %d: %s\n", cnt, line);
        }
        
        private final String line;
        private final long repeat;
    }

    public static void test(String line, long mainRepeat, long childRepeat) {
        // создаём объек MyThread, наследующий Thread.
        Thread t = new MyThread(line, childRepeat);
        // запускаем поток вызовом t.start()
        t.start();
        // выполняем свои действия
        for (long cnt = 0; cnt != mainRepeat; ++cnt)
            System.out.println("Main thread is working...");
        // основной main thread будет ждать завершения всех своих дочерних thread
    }
}

/**
 * Другой способ запуска потока - это передача в конструктор потока реализации интерфейса
 * Runnable, который содержит один единственный метод run.
 */
final class RunnableImplementExample {
    public static void test(final String line, final long mainRepeat, final long childRepeat) {
        // создаём объект типа Thread, передавая в конструктор реализацию интерфейса Runnable,
        // например с помощью анонимного класса
        Thread t = new Thread(
            new Runnable() {
                @Override
                public void run() {
                    for (long cnt = 0l; cnt != childRepeat; ++cnt)
                        System.out.format("Line %d: %s\n", cnt, line);
                }
            }
        );
        // запускаем поток вызовом t.start()
        t.start();
        // выполняем свои действия
        for (long cnt = 0; cnt != mainRepeat; ++cnt)
            System.out.println("Main thread is working...");
        // основной main thread будет ждать завершения всех своих дочерних thread
    } 
}

/**
 * Если перед запуском потока установить флаг setDeamon в true, то родительский поток
 * не будет ждать дочерний.
 */
final class DeamonExample {
    public static void test(final String line, final long mainRepeat, final long childRepeat) {
        // создаём объект типа Thread, передавая в конструктор реализацию интерфейса Runnable,
        // например с помощью анонимного класса
        Thread t = new Thread(
            new Runnable() {
                @Override
                public void run() {
                    for (long cnt = 0l; cnt != childRepeat; ++cnt)
                        System.out.format("Line %d: %s\n", cnt, line);
                }
            }
        );
        // отмечаем, что поток deamon
        t.setDaemon(true);
        // запускаем поток вызовом t.start()
        t.start();
        // выполняем свои действия
        for (long cnt = 0; cnt != mainRepeat; ++cnt)
            System.out.println("Main thread is working...");
        // основной main thread Не будет ждать завершения deamon thread
        // если у потока остались незавершённые задачи, то они не будут выполнены
    } 
}

/**
 * Синхронное ожидание исполнения потока.
 * Если для продолжения работы родительского потока нужно дождаться работы 
 * дочернего потока, то можно применить вызов t.join().
 * 
 * Рассмтрим следующую задачу: 
 *     для нахождения максимума в List<Long> мы разобьём его на два списка и запустим поиск максимума 
 *     в для каждой половины в отдельном потоке; чтобы получить конечный результат нам нужно дождаться 
 *     завершения работы обоих потоков.
 */
final class SplitMaximumExample {
    /**
     * Данный класс-обёртка нам нужен, чтобы вычисляющий поток
     * мог записать свой ответ в разделяемуму память
     */
    private static final class LongWrapper {
        public long get() { return this.l; }
        public void set(long l) { this.l = l; }
        private volatile long l; // переменная должна гарантировать чтение нового значения, если была перезаписана другим потоком
    }

    /**
     * Класс для реализации алгоритма по поиску максимума.
     * Мы пишием именно отдельный класс, так как будем создавать 
     * на его основе более одного потока.
     */
    private static final class MaximumRunnable implements Runnable {
        /**
         * Нам необходим список, в котором происходит поиск
         * и обёртка для записи результата
         */
        public MaximumRunnable(List<Long> lst, LongWrapper ans) {
            this.lst = lst;
            this.ans = ans;
        }

        @Override
        public void run() {
            var it = lst.iterator();
            long max = Long.MIN_VALUE;
            while (it.hasNext()) 
                max = Math.max(max, it.next());
            ans.set(max);
            System.out.println("Partial maximum found!");
        }

        private final List<Long> lst;
        private final LongWrapper ans; // это ссылка на объект LongWrapper, что делает его разделяемым
    }

    private static Optional<Long> splitCalculation(int numberOf, int splitIdx) {
        var array = new ArrayList<Long>();
        for (int cnt = 0; cnt != numberOf; ++cnt)
            array.add((long)cnt);
        
        List<Long> leftList = array.subList(0, splitIdx);
        List<Long> rightList = array.subList(splitIdx, numberOf);
        LongWrapper leftAns = new LongWrapper();
        LongWrapper rightAns = new LongWrapper();

        Thread leftT = new Thread(new MaximumRunnable(leftList, leftAns));
        Thread rightT = new Thread(new MaximumRunnable(rightList, rightAns));

        leftT.start();
        rightT.start();

        /**
         * мы не можем выполнять свою работу пока дочерние потоки не завершатся
         * однако метод join определяет checked exception InterruptedException,
         * который может возникнуть в ситуации, когда поток прерван извне
         */
        try {
            leftT.join();
            rightT.join();
        } catch (InterruptedException e) {
            System.out.println("One of the calculating threads has been interrupted");
            return Optional.empty();
        }

        return Optional.of(Math.max(leftAns.get(), rightAns.get()));
    }

    public static void test(int numberOf, int splitIdx) {
        var opt = splitCalculation(numberOf, splitIdx);
        if (opt.isPresent())
            System.out.format("Split calculation done: %d\n", opt.get());
    }
}

/**
 * Если потоку нужно некоторое время бездействовать, то можно вызывать на нём команду 
 * sleep(<milliseconds>)
 * Так же как и с ожиданием при join метод sleep может быть прерван!
 * Для прерывания потока используется метод interrupt
 * Именно с его помощью следует останавливать работающий поток. При этом поток не обязан
 * остановиться сразу, а может продолжать работать.
 * Сам поток узнаёт о прерывании, проверяя свойство isInterrupted
 */
final class InterruptionCheckerExample {
    /**
     * В данном примере запускается бесконечный Worker thred, 
     * который периодически проверяет, что его не прервали.
     * Родительский thread ждёт определённое время, а потом посылает сигнал на прерывание
     */
    public static void test(long waitMillis) {
        Thread t = new Thread( new Runnable() {
            @Override
            public void run() {
                long max = 1l;
                while (true) {
                    long sum = 0l;
                    for (long cnt = 0l; cnt != max; ++cnt)
                        sum += cnt;
                    System.out.format("\rSum to %d = %d", max, sum);
                    ++max;
                    
                    // так как мы не знаем самого объекта, который реализует Thread
                    // мы используем статические методы Thread
                    // в этом случае система определяет в каком именно потоке вызывается
                    // исполнение и затем вызывает нужный метод
                    if (Thread.interrupted()) { // if true - исполнение преравано извне
                        System.out.println("\nThe worker thread has been interrupted");
                        return;
                    }
                }
            }
        });

        t.start();
        try {
            Thread.sleep(waitMillis); // 1 sec
        } catch (InterruptedException e) {
            System.out.println("\nThe main thread has been interrupted");
            return;
        } finally {
            t.interrupt(); // выставляет флаг потоку, что он прерван
        }
        
        System.out.println("The main thread done!");
    }
}

/**
 * Так как при работе main thread все не deamon thread продолжают работать, то объекты Thread
 * не могут быть уничтожены сборщиком мусора даже после потери всех ссылок на эти объекты из 
 * нашей программы! Ссылки на thread удерживает сама JVM. Если случайно запустить процесс,  
 * забыть его остановить и потерять ссылку на объект Thread, то возникнит утечка ресурса. 
 * Такая утечка может быть очень критична для приложения, которое работает непрерывно долгое время.
 */

public final class ThreadExample {
    public static void main(String[] args) {
        if (false) ThreadInheritanceExample.test("Child line", 10, 10);
        if (false) RunnableImplementExample.test("Child line", 10, 10);
        if (false) DeamonExample.test("Child line", 10, 10);
        if (false) SplitMaximumExample.test(1000, 100);
        if (false) InterruptionCheckerExample.test(100); 
    }
}
